<template>
  <div class="editor-container">
    <div
      ref="editorRef"
      class="editor-content p-4 min-h-[300px] focus:outline-none"
      contenteditable="true"
      @compositionend="handleCompositionEnd"
      @input="handleInput"
      @keydown="handleKeydown"
      @paste="handlePaste"
      @mouseup="handleMouseUp"
      @keyup="handleKeyUp"
      @focus="handleFocus"
      @blur="handleBlur"
      @click="handleClick"
      :spellcheck="spellcheck"
      :class="{ 'is-empty': isEmpty }"
      data-placeholder="请输入文章内容..."
    />
    <HoverBar
      ref="hoverBarRef"
      @setTextColor="setTextColor"
      @setBackgroundColor="setBackgroundColor"
    />
  </div>
</template>

<script setup lang="ts">
import { useEditorStore } from "@/stores/editor";
import HoverBar from "./HoverBar.vue";

const editorStore = useEditorStore();
const editorRef = ref<HTMLElement>();
const hoverBarRef = ref();
const isEmpty = ref(true);

const spellcheck = defineModel<boolean>("spellcheck", { required: true });
const props = defineProps<{
  content: string;
}>();

const emit = defineEmits<{
  (e: "update:content", value: string): void;
}>();

const getIsEmpty = () => {
  const content = editorRef.value?.innerHTML || "";
  return !content || content === "<br>" || content === "<div><br></div>";
};

const handleCompositionEnd = (e: CompositionEvent) => {
  handleInput(e as unknown as InputEvent);
};

const handleInput = (e: InputEvent) => {
  if (e.isComposing) return;

  const content = editorRef.value?.innerHTML || "";
  isEmpty.value = getIsEmpty();
  editorStore.setContent(content);
  emit("update:content", content);
};

const handleKeydown = (e: KeyboardEvent) => {
  if (e.key === "Enter" && !e.shiftKey) {
    e.preventDefault();
    document.execCommand("insertParagraph", false);
  }
};

const handlePaste = (e: ClipboardEvent) => {
  e.preventDefault();
  const text = e.clipboardData?.getData("text/plain");
  if (text) {
    document.execCommand("insertText", false, text);
  }
};

const handleMouseUp = () => {
  const selection = window.getSelection();
  if (selection && selection.rangeCount > 0 && !selection.isCollapsed) {
    const range = selection.getRangeAt(0);
    hoverBarRef.value?.updatePosition(range);
  } else {
    hoverBarRef.value?.hide();
  }
};

const handleKeyUp = (e: KeyboardEvent) => {
  if (e.key === "ArrowLeft" || e.key === "ArrowRight") {
    handleMouseUp();
  }
};

const handleClick = (event: MouseEvent) => {
  const target = event.target as Node;
  const hoverBarEl = hoverBarRef.value?.barRef;

  if (hoverBarEl && !hoverBarEl.contains(target)) {
    hoverBarRef.value?.hide();
  }
};

const handleFocus = () => {
  if (isEmpty.value && editorRef.value) {
    editorRef.value.innerHTML = "";
  }
};

const handleBlur = () => {
  if (editorRef.value && !editorRef.value.innerHTML.trim()) {
    isEmpty.value = true;
    editorRef.value.innerHTML = "";
  }
};

const setTextColor = (color: string | null) => {
  if (!color) return;
  const selection = window.getSelection();
  if (!selection?.rangeCount) return;

  const range = selection.getRangeAt(0);
  const contents = range.extractContents();
  const span = document.createElement("span");
  span.style.color = color;
  span.appendChild(contents);
  range.insertNode(span);
  handleInput(null);
};

const setBackgroundColor = (color: string | null) => {
  if (!color) return;
  const selection = window.getSelection();
  if (!selection?.rangeCount) return;

  const range = selection.getRangeAt(0);
  const contents = range.extractContents();
  const span = document.createElement("span");
  span.style.backgroundColor = color;
  span.appendChild(contents);
  range.insertNode(span);
  handleInput(null);
};

const setContent = (content: string) => {
  if (editorRef.value) {
    editorRef.value.innerHTML = content;
    isEmpty.value = getIsEmpty();
  }
};

watch(
  () => props.content,
  (newContent) => {
    if (editorRef.value && editorRef.value.innerHTML !== newContent) {
      setContent(newContent);
    }
  }
);

const execute = (command: string) => {
  if (command === "undo") {
    console.log("undo");
    editorStore.undo();
    return;
  }
  if (command === "redo") {
    console.log("redo");
    editorStore.redo();
    return;
  }
  handleInput(null);
};

const focus = () => {
  editorRef.value?.focus();
};

defineExpose({
  execute,
  setContent,
  focus,
  setHoverBarRef: (ref) => {
    hoverBarRef.value = ref;
  },
});

onMounted(() => {
  if (editorRef.value) {
    setContent(props.content);
    isEmpty.value = getIsEmpty();
  }
});
</script>

<style scoped>
.editor-container {
  @apply h-full flex flex-col;
}

.editor-content {
  @apply flex-1 overflow-auto p-4 min-h-[300px] focus:outline-none relative;

  h1 {
    @apply text-2xl font-bold my-4;
  }

  h2 {
    @apply text-xl font-bold my-3;
  }

  blockquote {
    @apply border-l-4 border-gray-300 pl-4 my-4 italic;
  }

  ul {
    @apply list-disc list-inside my-2;
  }

  ol {
    @apply list-decimal list-inside my-2;
  }
}

.editor-content.is-empty:empty:before {
  @apply text-gray-400 pointer-events-none absolute text-sm left-4 top-4;
  content: attr(data-placeholder);
}

.editor-toolbar {
  .toolbar-group {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 0 8px;

    &:not(:last-child) {
      border-right: 1px solid var(--el-border-color-light);
    }
  }
}

:deep(.el-color-picker__trigger) {
  @apply border-none p-0;
}

:deep(.el-button) {
  @apply p-2 h-8;
}
</style>
